import { Injectable } from '@angular/core';
import { TrackWrapper, VideoContainer, VideoStatus, VideoZone, TrackType, VideoZonePos } from '../entity/ORTC';
import { OrtcService } from './ortc.service';
import { EventMessage, EventSource, EventService, ParticipantMuteChangeEvent, ParticipantSelectEvent } from './event.service';
import { RequestReplyService } from './request-reply.service';
import { Utils } from '../common/Common';
import { User, UserType, } from '../entity/TT';

const TAG = 'VideozoneService';


@Injectable({
  providedIn: 'root'
})
export class VideozoneService {

  private vzs = {};

  constructor(
    private rtcService: OrtcService,
    private eventService: EventService,
    private requestService: RequestReplyService,
  ) {

    console.log(TAG, '.init ...');

    this.eventService.subscribeEvent((evt: EventMessage) => {
      switch (evt.source) {

        case EventSource.USER_JOINED: {
          const tw: TrackWrapper = evt.data;
          this.onUserJoined(tw);
          break;
        }

        case EventSource.USER_LEFT: {
          const tw: TrackWrapper = evt.data;
          this.onUserLeft(tw);
          break;
        }

        case EventSource.CLOCK_TICK: {
          this.keepalive();
          break;
        }

        case EventSource.SELECT_ALL: {
          const active: boolean = evt.data;
          this.selectAll(active);
          break;
        }

        case EventSource.MUTE: {
          const active: boolean = !evt.data;
          const participants = this.selectedParticipants();
          this.rtcService.sendControllerVoice(participants, active);
          break;
        }

        case EventSource.MUTE_CHANGED: {
          const pmce: ParticipantMuteChangeEvent = evt.data;
          this.setMuteChange(pmce);
          break;
        }

        case EventSource.CONTROLLER_WHITE_BOARD: {
          const active: boolean = evt.data;
          const participants = this.selectedParticipants();
          this.rtcService.sendControllerWhiteBoard(participants, active);
          break;
        }

        case EventSource.GIFT_COIN: {
          const active: number = evt.data;
          this.sendCoin(active);
          break;
        }

        case EventSource.GIFT_FABULOUS: {
          const active: number = evt.data;
          this.sendFabulous(active);
          break;
        }

        case EventSource.GIFT_CHANGED: {
          const participants: string[] = evt.data;
          this.refreshGift(participants);
          break;
        }

        case EventSource.PARTICIPANT_GO_HOME: {
          const participants = this.selectedParticipants();
          this.rtcService.sendControllerStudentGoHome(participants, true);
          break;
        }




      }
    });

  }

  refreshGift(participants: string[]) {
    const vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);

    participants.forEach(participant => {

      this.requestService.GetUser(Utils.GetUinFromParticipant(participant))
      .subscribe((user: User) => {
        if (user) {
          const list =  this.listByParticipantId(vz, participant);
          list.forEach(s => {
            s.user = user;
          });
        }
      });
    });

  }

  setMuteChange(pmce: ParticipantMuteChangeEvent) {
    const vc = this.getByParticipantId(pmce.participantId);
    vc.isMuted = pmce.active;
  }
  
  sendFabulous(active: number) {
    const participants = this.selectedParticipants();
    const uins = participants.map(p => Utils.GetUinFromParticipant(p));
    this.requestService.GiveFabulous(uins, active).subscribe((r: boolean) => {
      if (r) {
        this.rtcService.sendControllerGiftFabulous(participants, active);
      }
    });
  }

  sendCoin(active: number) {
    const participants = this.selectedParticipants();
    const uins = participants.map(p => Utils.GetUinFromParticipant(p));
    this.requestService.GiveCoin(uins, active).subscribe((r: boolean) => {
      if (r) {
        this.rtcService.sendControllerGiftCoin(participants, active);
      }
    });
  }

  keepalive() {
    for (const key in this.vzs) {
      const vz = this.vzs[key];
      this.listAll(vz).forEach((vc: VideoContainer) => {
        const tw: TrackWrapper = this.rtcService.getRemoteTrack(vc.participantId);
        this.bind(vz, vc, tw);
      });
    }

  }

  bind(vz: VideoZone, vc: VideoContainer, tw: TrackWrapper) {
    if (!tw) {
      return;
    }

    for (const type in tw.trackMap) {

      const ot = vc[type + 'Track'];
      const nt = tw.trackMap[type];

      if (!ot || ot.track.id != nt.track.id) {
        this.setTrack(vz, vc, TrackType[type], nt);
      }

    }

  }

  onUserJoined(tw: TrackWrapper) {
    console.log(TAG, 'onUserJoined', tw);
    const uin = Utils.GetUinFromParticipant(tw.participant);
    this.requestService.GetUser(uin).subscribe((user: User) => {
      this.setUser(user, tw);
    });
  }

  onUserLeft(tw: TrackWrapper) {
    console.log(TAG, 'onUserLeft', tw);
    const uin = Utils.GetUinFromParticipant(tw.participant);
    this.requestService.GetUser(uin).subscribe((user: User) => {
      this.unsetUser(user);
    });
  }

  unsetUser(user: User) {
    let vc: VideoContainer = this.getByUser(user);
    if (!vc) {
      const vz = this.getVideoZoneByUser(user);
      if (vz) {
        vc = this.getEmpty(vz);
      }
    }

    if (vc) {
      vc.user = null;
      vc.participantId = null;
      vc.status = VideoStatus.NONE;
    }

  }

  setUser(user: User, tw: TrackWrapper) {
    let vc: VideoContainer = this.getByUser(user);
    if (!vc) {
      const vz = this.getVideoZoneByUser(user);
      if (vz) {
        vc = this.getEmpty(vz);
      }
    }

    if (vc) {

      vc.user = user;
      vc.participantId = tw.participant;
      vc.status = VideoStatus.ATTACHING;
    }
  }

  unsetTrack(vz: VideoZone, vc: VideoContainer, type: TrackType, track: any) {
    const selector: string = type + '#' + type + '_' + vc.ownerId;
    // console.log(TAG, 'unsetTrack', vc, selector, track);
    const v = vz.elementRef.nativeElement.querySelector(selector);
    if (v && track) {
      // console.log(TAG, 'detach', track, v)
      track.detach(v);

    }
  }

  setTrack(vz: VideoZone, vc: VideoContainer, type: TrackType, track: any) {
    const selector: string = type + '#' + type + '_' + vc.ownerId;
    // console.log(TAG, 'setTrack', vc, selector, track);
    const v = vz.elementRef.nativeElement.querySelector(selector);
    if (v && track) {
      // console.log(TAG, 'attach', track, v)
      track.attach(v);
      vc.status = VideoStatus.ATTACHED;
      vc[track.getType() + 'Track'] = track;
    }
  }

  listByParticipantId(vz: VideoZone, participantId: string) {
    return this.listAll(vz).filter(vc => vc.participantId == participantId);
  }

  getByParticipantId(participantId: string) {
    
    let p = this.listAll(this.getVideoZoneByPos(VideoZonePos.VZ_Pos2)).find(vc => vc.participantId == participantId);
    if(!p){
      p = this.listAll(this.getVideoZoneByPos(VideoZonePos.VZ_Pos1)).find(vc => vc.participantId == participantId);
    }
    
    return p;
  }

  getByUser(user: User) {
    if(!user){
      console.warn("user is invalid");
      return;
    }
    const vz: VideoZone = this.getVideoZoneByUser(user);
    return this.getByUin(vz, user.uin);
  }

  getVideoZoneByUser(user: User): VideoZone {
    
    let vz: VideoZone = null;
    if (user.userType === UserType.Member) {
      vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);
    } else if (user.userType === UserType.Teacher) {
      vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos1);
    }

    return vz;
  }

  getByUin(vz: VideoZone, uin: string): VideoContainer {
    return this.listAll(vz).find(vc => vc.user && vc.user.uin == uin);
  }

  getEmpty(vz: VideoZone): VideoContainer {
    return vz.videos.find(vc => vc.user == null);
  }

  selectedParticipants() {
    const students = this.getAll();
    return students.filter(vc => vc.active).map(vc => vc.participantId);
  }

  allParticipants() {
    const students = this.getAll();
    return students.map(vc => vc.participantId);
  }

  selectAll(active: boolean, send: boolean= true) {
    const students = this.getAll()
    students.forEach(s => {
      s.active = active;
    });
 
    if (send) {
       
      const  ids = this.allParticipants()
      console.log('ids', ids)
      setTimeout(()=>{
        const pse: ParticipantSelectEvent = {
          participantIds: ids,
          select: active
        };
        this.eventService.sendEvent(EventSource.PARTICIPANT_SELECT, pse);
      },400)
    }
  }

  getAll(){
    const vz = this.getVideoZoneByPos(VideoZonePos.VZ_Pos2);
    const students = this.listAll(vz).filter(vc => vc.user);
     

    const vz2 = this.getVideoZoneByPos(VideoZonePos.VZ_Pos1);
    const teachers = this.listAll(vz2).filter(vc => vc.user);
     
    return students.concat(teachers)
  }
  

  listAll(vz: VideoZone): VideoContainer[] {
    const list: VideoContainer[] = [];

    if (vz) {

      const mates = vz.videos;
      for (const mate of mates) {
        if (mate.user) {
          list.push(mate);
        }
      }
    }

    return list;
  }

  getVideoZoneByPos(pos: VideoZonePos): VideoZone {
    return this.vzs[pos];
  }

  register(vz: VideoZone) {
    console.log(TAG, 'register', vz);
    this.vzs[vz.position] = (vz);

    const rs: TrackWrapper[] = this.rtcService.getRemoteTracks();
    rs.forEach((tw) => {
      this.onUserJoined(tw);
    });

  }

  unregister(vz: VideoZone) {
    console.log(TAG, 'unregister', vz);
    delete this.vzs[vz.position];
  }
}
